iT邦幫忙

2022 iThome 鐵人賽

DAY 11
0

終...終於到了實作的部分了,鴿了這麼久,想必讀者也習慣了吧(不過有沒有人看我也不知道)。到今天也差不多體會到鐵人賽的驚險了?!沒屯稿的話每天被文章追著跑。

架構

那事不宜遲,我們開始吧。首先要先提到的是遊戲引擎的架構,我們的解決方案中會需要兩個專案。

  • Engine
    首先我們會需要一個動態庫,這個函式庫將會包含我們整個引擎。遊戲本體會透過動態連接的方式引用我們的引擎。至於為什麼是使用動態庫而非靜態庫,我們稍後會在下面做說明。

  • Sandbox
    接著我們需要一個應用程式,這是我們的遊戲本體。

LIB vs DLL

  • LIB
    靜態庫是一種副檔名為.lib的二進位文件,編譯與Linking時Linker會從函式庫裡面複製Function和Data與應用程式組合併生成最後的可執行檔案.exe,這種做法只需要可執行檔案就能執行,不需要一併附上靜態庫.lib檔案。

  • DLL
    動態庫編譯時會生成兩個文件,.lib檔、.dll檔。而引用動態庫與靜態庫有頗大的區別,.lib檔包含DLL導出的函數和變數,.dll檔案則是包含完整實際的Function和Data。編譯與Linking時只需要Linking .lib檔,而.dll檔則是在應用程式運行時才會加載DLL,動態的訪問函式庫去導出所需要的Function或Data。這樣輸出時需要可執行檔案和DLL才能執行。

因此我們可以知道,靜態函式庫與動態函式庫雖然都可以達到一樣的效果,但是使用動態函式庫時需要一併附上DLL檔案,若是DLL檔案遺失則會造成無法執行的錯誤;但靜態函式庫裡會將所有需要的Function或Data包含在最終的執行檔中,造成檔案會比較大。那在於我們的專案中會使用許多的第三方函式庫,若是以靜態庫的方式去連接會造成遊戲本體非常的龐大,這也是我們選擇使用動態函式庫連接的原因。

實作

接著可以打開前幾天示範使用的test資料夾。我們可以先將前幾天的src資料夾刪除,我們等等會來重新配置專案。

CMake

接下來我們可以來重新配置CMakeLists,這裡的include是幾天前CMake章節中沒有提到的部分,他會尋找並執行你給定檔案的CMake code,這麼做的用意是可以更有系統的去配置你的專案,也更易讀。由於我們需要一個引擎的動態庫與一個名為Sandbox的應用程式,也先建立兩個以此為名的資料夾與同名的CMake檔

檔案配置如下

test
├─ CMakeLists.txt
├─ GenerateProject.bat
├─ engine
│    └─  engine.cmake
└─  sandbox
     └─  sandbox.cmake

先將CMakeLists做修改。

  • CMakeLists
cmake_minimum_required(VERSION 3.21)

project(Engine
    VERSION 0.0.0
    LANGUAGES CXX
)

set(CMAKE_CXX_STANDARD 17)

include(engine/engine.cmake)
include(sandbox/sandbox.cmake)

接著先寫一段動態庫的CMake模板。

  • engine.cmake
# 這裡等等會隨著進度加上函式庫的原始碼與標頭檔
set(SOURCES
)

set(HEADERS
)

# 建立專案,名為engine,並將
add_library(engine SHARED ${SOURCES} ${HEADERS})

# 未來添加第三方函式庫時需要
target_link_libraries(engine
)

# 未來添加include目錄時需要
target_include_directories(engine PRIVATE
)
 
# 未來添加definitions時需要
target_compile_definitions(engine PRIVATE
)

接著建立一段應用程式的模板。可以發現這裡與上面動態庫的模板多了一個add_dependencises,這是要將應用程式與動態庫建立依賴,提醒編譯器要先生成依賴項再生成應用程式以免出錯。

  • sandbox.cmake
# 這裡等等會隨著進度加上應用程式的原始碼與標頭檔
set(SOURCES
)

set(HEADERS
)

# 建立應用程式專案
add_executable(sandbox ${SOURCES} ${HEADERS})

# 新增依賴項
add_dependencies(sandbox
  engine
)

# 未來添加第三方函式庫時需要
target_link_libraries(sandbox
  engine
)

# 未來添加include目錄時需要
target_include_directories(sandbox PRIVATE
)

# 未來添加definitions時需要
target_compile_definitions(sandbox PRIVATE
)

Engine

Application Class

首先,我們先在Engine底下建立src/Core資料夾作為核心部分原始碼儲存部分,然後建立一個Application Class作為我們引擎的本體。而這個Class最後將在Sandbox中使用,因此我們需要加上__declspec(dllexport)的關鍵字,用於從 DLL 匯出資料、函數、類別或類別成員函式的關鍵字。在Class裡面,我們定義了Run(),用於啟動應用程式。與一個會回傳Application指標的Function CreateApplication(),他將會在應用程式宣告時被定義。

  • Core/Application.h
#pragma once /* 預處理的指令,保證標頭檔只被編譯一次 */

/* 引擎相關的都會被放在Engine namespace */
namespace Engine {

    class __declspec(dllexport) Application
    {
    public:
        Application();
        ~Application();

        void Run();
    };

    Application* CreateApplication();
}

接著,我們在實現的部分。Run()為啟動引擎,就先簡單的跑一個無限迴圈。

  • Core/Application.cpp
#include "Application.h"

namespace Engine {
    Application::Application() {}
    Application::~Application() {}

    void Application::Run()
    {
        while (true)
        {
            /* code */
        }
    };
}

最後是Entry Point的部分,引擎的進入點、也是整個動態函式庫的進入點。extern是引用外部變數的意思,因為我們的CreateApplication()並非定義在這個檔案中。而我們在啟動app之前也能先簡單的用標準輸出來印個"Welcome to Engine!\n"。

EntryPoint

  • Core/EntryPoint.h
#pragma once

extern Engine::Application* Engine::CreateApplication();

int main(int argc,char** argv)
{
    printf("Welcome to Engine!\n");
    auto app = Engine::CreateApplication();
    app->Run();
    delete app;
}

這裡我們也寫一個包含所有引擎內容的標頭檔。注意這裡的順序,由於EntryPoint使用了print函式,因此stdio的header必須在其前面。

Engine Header

  • Engine.h
#pragma once

#include<stdio.h>

#include "Core/Application.h"
#include "Core/EntryPoint.h"

Sandbox

然後我們到Sandbox底下建立src資料夾,並建立一個App。這裡就先include Engine 的all Header。並且將CreateApplication()定義,這個檔案以後我們在擴充時再細講他的用途。

  • App.cpp
#include <Engine.h>

Engine::Application* Engine::CreateApplication()
{
    return new Engine::Application();
}

資料夾架構

寫完這幾個檔案後我們的資料夾架構會是這樣。

test
├─ CMakeLists.txt
├─ GenerateProject.bat
├─ engine
│    ├─ engine.cmake
│    ├─ Engine.h
│    └─ src
│           └─ Core
│                  ├─ Application.cpp
│                  ├─ Application.h
│                  └─ EntryPoint.h
└─  sandbox
     ├─ sandbox.cmake
     └─ src
            └─ App.cpp

修改

到這裡還並沒有結束,我們新增了不少檔案,我們將它們加進去CMake中。

  • engine.cmake
set(SOURCES
    engine/src/Core/Application.cpp
    engine/src/Core/Application.h
    engine/src/Core/EntryPoint.h
)

set(HEADERS
    engine/Engine.h
)

add_library(engine SHARED ${SOURCES} ${HEADERS})

target_link_libraries(engine
)

target_include_directories(engine PRIVATE
    ${CMAKE_SOURCE_DIR}/engine
    ${CMAKE_SOURCE_DIR}/engine/src
)

target_compile_definitions(engine PRIVATE
)
  • sandbox.cmake
set(SOURCES
  sandbox/src/App.cpp
)

set(HEADERS
)

add_executable(sandbox ${SOURCES} ${HEADERS})

add_dependencies(sandbox
  engine
)

target_link_libraries(sandbox
  engine
)

target_include_directories(sandbox PRIVATE
    ${CMAKE_SOURCE_DIR}/engine
    ${CMAKE_SOURCE_DIR}/engine/src
)

target_compile_definitions(sandbox PRIVATE
)

完成後我們就能建置與執行了。

後記

今天完成了基礎的CMake的設置與應用程式的進入點,我們明天開始就慢慢加東西進去吧!


上一篇
「中場休息」—— 閒聊
下一篇
「引擎實作」—— 日誌系統 Logging system
系列文
《今天也走在開發遊戲引擎的路上》12
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言